#include "v4l2.h"
#include "ui_v4l2.h"
#include <QtGui/QScreen>
#include <QDateTime>
#include <QSocketNotifier>
#include <QDebug>

#define VIDEO_DEV "/dev/video0"

bool v4l2::handler_sign = false;


v4l2::v4l2(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::v4l2)
{    
    myinfo("*** 开始运行！***");
    ui->setupUi(this);

    ui->pushButton_take->setEnabled(false);  //不可用，未打开摄像头
    ui->pushButton_timing->setEnabled(false);  //不可用，未打开摄像头

    timer_updatetime = new QTimer();  //用于更新时间的定时器
    connect(timer_updatetime, SIGNAL(timeout()), this, SLOT(update_time()));
    timer_updatetime->start(1000);

    timer_show = new QTimer();  //用于刷新画面的定时器
    connect(timer_show, SIGNAL(timeout()), this, SLOT(video_show()));

    timer_shoot = new QTimer(); //用于定时拍照的定时器
    connect(timer_shoot, SIGNAL(timeout()), this, SLOT(take_photo()));

    signal(SIGUSR1, v4l2::sig_handler);

    ui->comboBox_period->showPopup();

    //创建目录
    QString currentDir = QDir::currentPath();
    QString photoDirPath = currentDir + "/.." + "/photo";
    QDir photoDir(photoDirPath);
    if (!photoDir.exists()){
        if (photoDir.mkdir(photoDirPath))
            myinfo("创建 photo 文件夹成功");
        else
            myinfo("创建 photo 文件夹失败");
    }
    else
        myinfo("photo 文件夹已存在");

    QString timephotoDirPath = currentDir + "/.." + "/time-photo";
    QDir timephotoDir(timephotoDirPath);
    if (!timephotoDir.exists()){
        if (timephotoDir.mkdir(timephotoDirPath))
            myinfo("创建 time-photo 文件夹成功");
        else
            myinfo("创建 time-photo 文件夹失败");
    }
    else
        myinfo("time-photo 文件夹已存在");
}

v4l2::~v4l2()
{
    delete ui;
}

//打开 & 设置相机
int v4l2::v4l2_open()
{
    /* 1.打开摄像头设备 */
    video_fd = open(VIDEO_DEV, O_RDWR);
    if(video_fd < 0){
        perror("打开摄像头设备失败");
        return -1;
    }
    /* 3.获取摄像头的能力 (VIDIOC_QUERYCAP：是否支持视频采集、内存映射等) */
    struct v4l2_capability capability;
    if(0 == ioctl(video_fd, VIDIOC_QUERYCAP, &capability)){
        if((capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0){
            perror("该摄像头设备不支持视频采集！");
            ::close(video_fd);
            return -2;
        }
        if((capability.capabilities & V4L2_MEMORY_MMAP) == 0){
            perror("该摄像头设备不支持mmap内存映射！");
            ::close(video_fd);
            return -3;
        }
    }

    /* 4.枚举摄像头支持的格式           (VIDIOC_ENUM_FMT：MJPG、YUYV等)
      列举出每种格式下支持的分辨率      (VIDIOC_ENUM_FRAMESIZES) */
    struct v4l2_fmtdesc fmtdesc;
    memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  //设置视频采集设备类型
    int i = 0;
    while(1){
        fmtdesc.index = i++;
        // 获取支持格式
        if(0 == ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc)){
            printf("支持格式：%s, %c%c%c%c\n", fmtdesc.description,
                                            fmtdesc.pixelformat & 0xff,
                                            fmtdesc.pixelformat >> 8 & 0xff,
                                            fmtdesc.pixelformat >> 16 & 0xff,
                                            fmtdesc.pixelformat >> 24 & 0xff);
            // 列出该格式下支持的分辨率             VIDIOC_ENUM_FRAMESIZES & 默认帧率 VIDIOC_G_PARM
            // 1.默认帧率
            struct v4l2_streamparm streamparm;
            streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            if(0 == ioctl(video_fd, VIDIOC_G_PARM, &streamparm))
                printf("该格式默认帧率 %d fps\n", streamparm.parm.capture.timeperframe.denominator);
            // 2.循环列出支持的分辨率
            struct v4l2_frmsizeenum frmsizeenum;
            frmsizeenum.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            frmsizeenum.pixel_format = fmtdesc.pixelformat;   //设置成对应的格式
            int j = 0;
            printf("支持的分辨率有：\n");
            while(1){
                frmsizeenum.index = j++;
                if(0 == ioctl(video_fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum))
                    printf("%d x %d\n", frmsizeenum.discrete.width, frmsizeenum.discrete.height);
                else break;
            }
            printf("\n");
        }else break;
    }

    /* 5.设置摄像头类型为捕获、设置分辨率、视频采集格式 (VIDIOC_S_FMT) */
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;   /* 视频采集 */
    format.fmt.pix.width = video_width;          /* 宽 */
    format.fmt.pix.height = video_height;    	 /* 高 */
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;   /* 设置输出类型：MJPG */
    if(0 > ioctl(video_fd, VIDIOC_S_FMT, &format)){
        perror("设置摄像头参数失败！");
        ::close(video_fd);
        return -4;
    }

    /* 6.向内核申请内存 (VIDIOC_REQBUFS：个数、映射方式为mmap)
         将申请到的缓存加入内核队列 (VIDIOC_QBUF)
         将内核内存映射到用户空间 (mmap) */
    struct v4l2_requestbuffers requestbuffers;
    requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    requestbuffers.count = 4;    //申请缓存个数
    requestbuffers.memory = V4L2_MEMORY_MMAP;     //申请为物理连续的内存空间
    if(0 == ioctl(video_fd, VIDIOC_REQBUFS, &requestbuffers)){
        /* 申请到内存后 */
        for(unsigned i = 0; i < requestbuffers.count; i++){
            /* 将申请到的缓存加入内核队列 (VIDIOC_QBUF)              */
            struct v4l2_buffer buffer;
            buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buffer.index = i;
            buffer.memory = V4L2_MEMORY_MMAP;
            if(0 == ioctl(video_fd, VIDIOC_QBUF, &buffer)){
                /* 加入内核队列成功后，将内存映射到用户空间 (mmap) */
                userbuff[i] = (char *)mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_fd, buffer.m.offset);
                userbuff_length[i] = buffer.length;
            }
        }
    }
    else{
        perror("申请内存失败！");
        ::close(video_fd);
        return -5;
    }
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(0 > ioctl(video_fd, VIDIOC_STREAMON, &type)){
        perror("打开视频流失败！");
        return -6;
    }
    return 0;
}

int v4l2::v4l2_close()
{
    /* 8.停止采集，关闭视频流 (VIDIOC_STREAMOFF)
      关闭摄像头设备 & 关闭LCD设备 */
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(0 == ioctl(video_fd, VIDIOC_STREAMOFF, &type)){
        /* 9.释放映射 */
        for(int i = 0; i < 4; i++)
            munmap(userbuff[i], userbuff_length[i]);
        ::close(video_fd);
        myinfo("相机已关闭!");
        return 0;
    }
    return -1;
}

void v4l2::update_time()
{
    QDateTime now = QDateTime::currentDateTime();
    QString timeString = now.toString("yyyy-MM-dd HH:mm:ss");
    ui->label_time->setText(timeString);
    //等待视频合成完成的信号
    if(handler_sign)
    {
        finish_video = true;
        if(open_camera)
        {
            ui->pushButton_timing->setEnabled(true);  //打开摄像头并且视频合成完成
        }
    }
}

void v4l2::video_show()
{
    QPixmap pix;

    /* 采集图片数据 */
    //定义结构体变量，用于获取内核队列数据
    struct v4l2_buffer buffer;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* 从内核中捕获好的输出队列中取出一个 */
    if(ioctl(video_fd, VIDIOC_DQBUF, &buffer) == 0){
        /* 显示在label_pic控件上 */
        //获取一帧显示
        if(pix.loadFromData((unsigned char *)userbuff[buffer.index], buffer.bytesused))
        {
            //pix = pix.convertToFormat(QImage::Format_RGB888);
            pix.scaled(ui->label_pic->width(),ui->label_pic->height(),Qt::KeepAspectRatio);
            ui->label_pic->setPixmap(pix);
        }
    }
    /* 将使用后的缓冲区放回到内核的输入队列中 (VIDIOC_QBUF) */
    if(ioctl(video_fd, VIDIOC_QBUF, &buffer) < 0)
    {
        perror("返回队列失败！");
    }    
}

/* 定时拍照函数 */
void v4l2::take_photo()
{
    //照片名字为时间戳
    QDateTime now = QDateTime::currentDateTime();
    QString str = "../time-photo/" + now.toString("MMddHHmmss")+".jpg";
    /* 采集图片数据 */
    //定义结构体变量，用于获取内核队列数据
    struct v4l2_buffer buffer;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* 从内核中捕获好的输出队列中取出一个 */
    if(0 == ioctl(video_fd, VIDIOC_DQBUF, &buffer)){
        //保存到本地
        int fd = open(str.toStdString().data(), O_RDWR | O_CREAT, 0777);   //打开并创建一个新文件
        write(fd, userbuff[buffer.index], buffer.bytesused);
        myinfo("定时拍照 %s", str.toStdString().data());
        ::close(fd);
    }
    /* 将使用后的缓冲区放回到内核的输入队列中 (VIDIOC_QBUF) */
    if(0 > ioctl(video_fd, VIDIOC_QBUF, &buffer)){
        perror("返回队列失败！");
    }
}



/* 控制相机打开和关闭 */
void v4l2::on_pushButton_open_clicked()
{
    if(open_camera == false)
    {
        if(v4l2_open() == 0)  // 使用v4l2打开 & 设置相机成功
        {
            // 由于摄像头默认30帧每秒,虽然10ms定时执行一次,但实际上1秒内最多有30次可以执行成功
            // 其余都会在ioctl处阻塞           
            timer_show->start(33);
            ui->pushButton_open->setText("关闭");
            open_camera = true;
            ui->pushButton_take->setEnabled(true);  //已打开摄像头
            if(finish_video)
            {
                ui->pushButton_timing->setEnabled(true);  //打开摄像头并且视频合成完成
            }
        }
    }
    else
    {
        if(v4l2_close() == 0){
            timer_show->stop();
            ui->pushButton_open->setText("打开");
            open_camera = false;
            ui->pushButton_take->setEnabled(false);  //不可用，未打开摄像头
            ui->pushButton_timing->setEnabled(false);  //不可用，未打开摄像头
        }
    }
}

/* 拍照 */
void v4l2::on_pushButton_take_clicked()
{
    //照片名字为时间戳
    QDateTime now = QDateTime::currentDateTime();
    QString str = "../photo/" + now.toString("yyyyMMddHHmmss")+".jpg";
    /* 采集图片数据 */
    //定义结构体变量，用于获取内核队列数据
    struct v4l2_buffer buffer;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* 从内核中捕获好的输出队列中取出一个 */
    if(0 == ioctl(video_fd, VIDIOC_DQBUF, &buffer)){
        //保存到本地
        int fd = open(str.toStdString().data(), O_RDWR | O_CREAT, 0777);   //打开并创建一个新文件
        write(fd, userbuff[buffer.index], buffer.bytesused);
        myinfo("手动拍照 %s", str.toStdString().data());
        ::close(fd);
    }
    /* 将使用后的缓冲区放回到内核的输入队列中 (VIDIOC_QBUF) */
    if(0 > ioctl(video_fd, VIDIOC_QBUF, &buffer)){
        perror("返回队列失败！");
    }

    //清空label_pic,相当于拍照效果提示
    ui->label_pic->clear();

}

/* 跳转到 showphoto ui界面 */
void v4l2::on_pushButton_photos_clicked()
{
    showphoto *s = new showphoto();
    s->show();
}

void v4l2::on_pushButton_video_clicked()
{

}

/* 定时拍照 */
void v4l2::on_pushButton_timing_clicked()
{   
    if(taking_photo == false)
    {
        myinfo("定时拍照周期为：%d s",time_period / 1000);
        v4l2::take_photo();
        timer_shoot->start(time_period);
        ui->pushButton_timing->setText("关闭定时");
        taking_photo = true;
    }
    else
    {
        timer_shoot->stop();
        ui->pushButton_timing->setText("定时拍照");
        ui->pushButton_timing->setEnabled(false);  //不可用，等待视频合成完成
        taking_photo = false;
        finish_video = false;  //定时拍照结束，给合成视频发信号
        handler_sign = false;
        //获取 makevideo 进程号
        FILE *fp;
        char pid_buffer[20];
        fp = popen(CMD_PID, "r");  //管道
        if(fgets(pid_buffer, sizeof(pid_buffer), fp) == NULL)
        {
            myerror("makevideo 进程不存在！");
        }
        else
        {
            int pid = atoi(pid_buffer);
            ::kill(pid, SIGUSR2);
        }
        pclose(fp);
    }
}

//定时拍照的周期
void v4l2::on_comboBox_period_currentTextChanged(const QString &arg1)
{
    QString numberPart = arg1.left(arg1.length() - 1);  //去掉末尾的 's'
    myinfo("更改定时拍照周期为：%d s",numberPart.toInt());
    time_period = numberPart.toInt() * 1000;
}

//处理收到的信号
void v4l2::sig_handler(int sig)
{
    if(sig == SIGUSR1)
    {
        myinfo("收到来自makevideo的信号SIGUSR1");
        handler_sign = true;
    }

}
